[Previous] [Next]

The DHTML Object Model

To write effective Dynamic HTML applications, you must become familiar with the object model exposed by the browser that hosts the DHTML page. Figure 19-4 shows the complete Window object hierarchy. I'm not going to dissect every property, method, and event in this hierarchy; instead, I'll focus on the objects that are the most interesting and useful to a Visual Basic programmer.

Click to view at full size.

Figure 19-4. The DHTML object model.

The Window Object

The Window object is the root of the DHTML hierarchy. It represents the window inside which the HTML page (which is represented by the Document object) is displayed.

Properties

If a window contains frames, you can access them by using the Frames collection, which contains other Window objects. Several other properties return a reference to Window objects. If the window is itself inside a frame, you can get a reference to its container window with the parent property. The top property returns a reference to the topmost window. In general, you can't anticipate which window is referenced by the latter two properties because the user might have loaded the page in a frame created by another HTML page external to your application. The opener property returns a reference to the window that opened the current one.

You can query the open or closed state of the window using the closed property. The status property sets and returns the text displayed in the browser's status bar, and defaultStatus is the default string in the status bar.

Methods

The Window object exposes several methods. The open method loads a new document in the window, and showModalDialog loads an HTML page in a modal window:

' Jump to another page.
window.open "http://www.vb2themax.com/tips"

You can close the window using the close method. A few other methods—alert, confirm, and prompt—display message box and input box dialog boxes, but you usually get a better result using Visual Basic's MsgBox and InputBox commands.

The focus method is similar to Visual Basic's SetFocus method. The blur method moves the input focus to the next window, as if the user had pressed the Tab key. The scroll method accepts an x-y pair of coordinates and scrolls the window to ensure that the specified point is visible in the browser:

' Scroll the window to the top.
window.scroll 0, 0

The execScript method adds a lot of flexibility to your program because it lets you manufacture a piece of script code and execute it on the fly. The following example uses this method to implement a cheap calculator with just a bunch of lines of code (see Figure 19-5):

Insert your expression here:
<INPUT TYPE=Text NAME="Expression" VALUE=""><BR>
Then click to evaluate it:
<INPUT TYPE=BUTTON NAME="Evaluate" VALUE="Evaluate">
<INPUT TYPE=Text NAME="Result" VALUE="">

<SCRIPT LANGUAGE="VBScript">
Sub Evaluate_onclick
    If Expression.Value = "" Then
        MsgBox "Please enter an expression in the first field"
        Exit Sub
    End If

    On Error Resume Next
    window.execScript "Result.value = " & Expression.Value, "VBScript"
    If Err Then
         MsgBox "An error occurred - please type a valid expression"
    End If 
     End Sub
</SCRIPT>

Click to view at full size.

Figure 19-5. An expression evaluator built with DHTML code.

Don't forget to pass the "VBScript" string as a second argument to the execScript function because the default language is JavaScript.

This powerful method can even add script procedures to the page. The following example demonstrates this feature by creating a table of values using an expression the user entered in a TextBox control. (This is something that would be very difficult to do in Visual Basic!)

Enter an expression:   FN(x) = 
<INPUT TYPE=Text NAME="Expression" VALUE="x*x"><P>
Click here to generate a table of values:
<INPUT TYPE=BUTTON NAME="CreateTable" VALUE="Create Table">

<SCRIPT LANGUAGE="VBScript">
Sub CreateTable_onclick()
' Create the FUNC() function. 
' (Enter the following two lines as a single VBScript statement,)
window.execScript "Function FUNC(x): FUNC = " 
    & Expression.Value & ": End Function", "VBScript"
' Create the HTML code for the table.
code = "<H1>Table of values for FN(x) = " & Expression.Value & "</H1>"
code = code & "<TABLE BORDER>"
code = code & "<TR><TH>  x  </TH><TH>  FN(x)  </TH></TR>"
For n = 1 To 100
   code = code & "<TR><TD> " & n & " </TD>"
   code = code & "<TD> " & FUNC(n) & " </TD></TR>"
Next
code = code & "</TABLE>"

' Write the code to a new HTML page.
window.document.clear
window.document.open
window.document.write code
window.document.close
End Sub
</SCRIPT>

I've already explained the remaining methods of the Window object: setInterval, setTimeout, clearInterval, and clearTimeout. (See the section "Timer Events" earlier in this chapter.)

The History object

The History object represents all the URLs that the user has visited in the current session. It exposes only one property and three methods.

The length property returns the number of URLs stored in the object. The back and forward methods load the page at the previous and next URL in the history list and therefore correspond to a click on the Back and Forward buttons on the browser's toolbar. They're useful for adding buttons on the page that perform the same function, as here:

Sub cmdPrevious_onclick()
    window.history.back
End Sub

The only other method of this object is go, which loads the URL at the nth position in the history list:

' Display the third page in the history list.
window.history.go 3

The Navigator object

The Navigator object represents the browser program and provides information about it capabilities. The appCodeName, appName, and appVersion properties return the code name, the product name, and the version of the browser. The cookieEnabled property returns True if the browser supports cookies; userAgent is the browser name sent as a string to the server in the HTTP request. You can use the following VBScript code to display some information about your browser:

' Dynamically create an HTML page with all the requested information.
Set doc = window.document
Set nav = window.navigator
doc.open
doc.write "<B>appCodeName</B> = " & nav.appCodeName & "<BR>"
doc.write "<B>appName</B> = " & nav.appName & "<BR>"
doc.write "<B>appVersion</B> = " & nav.appVersion & "<BR>"
doc.write "<B>cookieEnabled</B> = " & nav.cookieEnabled & "<BR>"
doc.write "<B>userAgent</B> = " & nav.userAgent & "<BR>"
doc.close

A few other properties return information about the browser, including cpuType, userLanguage, systemLanguage, and platform. The only method worth noting is javaEnabled, which returns True if the browser supports the Java language.

The Navigator object also exposes two collections: The mimeTypes collection includes all the document and file types that the browser supports, and the plugins collection contains all the objects in the page.

The Location object

The Location object represents the URL of the page currently displayed in the browser. Its most important property is href, which returns the complete URL string. All the other properties contain a portion of the URL string: hash (the portion following the # symbol), hostname (the name of the host), host (the hostname:port portion of the URL), port (the port number), protocol (the first part of the URL that holds the protocol name), and search (the portion following the ? in the URL).

This object also exposes three methods: assign (loads a different page), replace (loads a page and substitutes the current entry in the history list), and replace (reloads the current page).

The Screen object

The Screen object exposes properties about the screen and doesn't support any methods. The width and height properties are the dimensions of the screen in pixels and can be useful when you're deciding where to place a new window. The colorDepth property is the number of color planes supported and is typically used when the server site contains several similar images and you want to download the one that best fits the number of colors supported by the user's video card. The bufferDepth is a writable property that corresponds to the color depth of the off-screen buffer that the browser uses to display images. This property lets you display an image with a color depth different from its original color depth. The updateInterval property can be assigned the interval at which the browser redraws the page. (This is especially useful to reduce flickering when you're doing animations.)

Internet Explorer also supports the availWidth and availHeight properties, which return the size of the screen that isn't occupied by visible taskbars (such as the Microsoft Windows and the Microsoft Office taskbars), and the fontSmoothingEnabled Boolean property, which specifies whether the browser should use smoother fonts if necessary.

The Event object

I explained many features of the Event object earlier in this chapter. This object is used within VBScript event procedures to read and possibly modify event arguments, to specify whether the default action should be canceled, and to cancel event bubbling. The Event object exposes only properties, no methods.

Four pairs of properties return the coordinates of the mouse cursor.The screenX and screenY properties give you the position relative to the upper left corner of the screen, clientX and clientY give you the position relative to the upper left corner of the browser's client area; x and y give you the position relative to the container of the object that fired the event; and offsetX and offsetY give you the position relative to the object that fired the event. A ninth property, button, returns the state of the mouse button as a bit-fielded value (1=left button, 2=right button, 4=middle button).

Four properties have to do with the state of the keyboard: The keycode property is the ASCII code of the key that was pressed (and you can assign it to modify the effect of a key press), whereas altKey, ctrlKey and shiftKey return the state of the corresponding shift key.

Three properties return a reference to an element of the page. The scrElement is the item that originally fired the event; it can be different from the Me object if you're trapping the event within the event procedure of an object that's higher in the hierarchy. The fromElement and the toElement properties return the element being left and entered, respectively, during onmouseout or onmouseover events.

You can set the cancelBubble property to False to cancel event bubbling. You can set the returnValue property to False to cancel the default action associated with the event. The type property returns the name of the event without the leading on characters (such as click, focus, and so on).

The Document Object

The Document object represents the contents of the page currently loaded in the browser. It's probably the richest DHTML object in terms of properties, methods, events, and functionality.

Properties

Several properties return information about the state of the document and the page loaded in it. The title property contains the title of the document (the string defined by the <TITLE> tag); URL holds the Uniform Resource Locator of the page (such as http://www.vb2themax.com); domain returns the security domain of the document; and lastModified returns the date and time of the most recent edit operation for the document. The referrer property is the URL of the page that referred the current one.

Some properties set or return color values. For example, fgcolor and bgcolor give you the text and background colors of the page; changing these properties has an immediate effect on the page (except in the areas where specific color settings have been defined). Three properties control the color of hyperlinks: linkColor returns the color for links that haven't been visited yet; vLinkColor tells you the color for visited links, and alinkColor returns the color for active links (that is, links under the cursor when the mouse button is pressed).

A number of properties return references to other objects in the page. The body property gives you a reference to the Body object; parentWindow returns a reference to the Window object that this document belongs to; location is a reference to the Location object exposed by the parent Window; and activeElement is a reference to the page element that has the focus. (When you've just downloaded the page this property returns a reference to the Body element.)

The readyState property returns a string that describes the current download state of the document. This is a valuable information because it lets you avoid the errors that would occur if you reference an object—such as an image—while the page is still downloading:

If document.readyState = "complete" Then
    ' Fill the text box control only if the page has been completely
    ' downloaded.
    MyTextBox.Value = "Good morning dear user!"
End If

This property is so important that the Document object fires a special event, onReadyStateChange, when its value changes. Thanks to this event, you don't have to continuously poll this property to determine when it's safe to act on page elements.

Methods

We have already seen several methods of the Document object, namely the clear, open, write, and close methods that we've used to dynamically create new HTML pages. The writeln method is a variant of the write method that adds newline characters and supports multiple arguments:

document.writeln "First Line<BR>", "Second Line"

Remember that the added newline character usually has no effect if the output is pure HTML unless you're inserting text inside a pair of <PRE> and </PRE> tags or <TEXTAREA> and </TEXTAREA> tags.

The elementFromPoint method returns the element that corresponds to a given pair of coordinates (It's therefore is similar to the HitTest method exposed by a few Visual Basic controls.) You can use this method inside a mouse event procedure to display a description of the object under the mouse cursor:

Sub Document_onmousemove()
     On Error Resume Next     ' Not all elements have a Name property.
    ' Fill a text box with the description of the element under the mouse.
    Set element = document.elementFromPoint(window.event.x, window.event.y)
     Select Case element.Name
        Case "txtUserName"
            txtDescription.Value = "Enter your username here"
        Case "txtEmail"
            txtDescription.Value = "Enter your e-mail address here"
        ' And so on.
    End Select
End Sub 

Earlier in this chapter, I showed you how to use the Document's createElement method to create new Option objects and dynamically fill a Select control at run time. You can also use this method to create new <IMG> tags and <AREA> tags. (The latter tags create image maps, which aren't covered in this book, however.)

Child collections

The Document object exposes several child collections, which let you iterate on all the elements in the page. These collections aren't disjoint, so an element can belong to more than one collection. For example, the all collection gathers all the tags and the elements in the body of the document, but the Document object also exposes the anchors, images, and links collections, whose names are self-explanatory. The scripts collection contains all the <SCRIPT> tags, forms is the collection of existing forms, styleSheets gathers all the styles defined for the document. A few collections concern objects that I haven't covered in this chapter, such as the frames, embeds, and plugins collections.

Working with these collections is similar to working with Visual Basic's collections. You can refer to an element in the collection using its index (collections are zero-based) or key (in most cases the key corresponds to the name of the element). The most relevant difference is that DHTML collections support the length property instead of the Count property. You can iterate on all the items of a collection using a For Each...Next loop, and you can determine the type of an element by peeking at its tagName property:

' Print the tags of all the items in the page.
For Each x In document.all
    text = text & x.tagName & ", "
Next
text = Left(text, Len(text) _ 2)        ' Drop the last comma.
MsgBox text

If you want to retrieve only elements with a given tag, you can filter the collection using its tags method, which accepts the name of the tag you're filtering on:

' Display the names of all <INPUT> elements.
For Each x In document.all.tags("INPUT")
    text = text & x.Name & ", "
next
MsgBox Left(text, Len(text) _ 2)        ' Drop the last comma.

The tags method returns a collection, so you can store its return value in a variable for later. Or you can query its length property:

Set imgCollection = document.all.tags("IMG")
MsgBox "Found " & imgCollection.length & " images."

The forms collection is special, in that it exposes the child elements collection, which in turn contains all the controls in the form. A page can contain multiple forms, even though all the examples in this chapter use only one form:

' List the names of all the controls on the first form in the page.
For Each x In document.forms(0).elements
    text = text & x.name & ", "
Next
MsgBox Left(text, Len(text) _ 2)

' Move the input focus on the control named "txtUserName"
' of the form named "UserData."
document.forms("UserData").elements("txtUserName").focus

The Selection object

The Selection object represents the portion of the document that's currently highlighted by the user. Its only property is type, which returns a string that tells which kind of elements are selected. (The choices are none or text.)

The Selection object supports three methods. The empty method cancels the selection and reverts its type property to none. The clear method actually deletes the contents of the selection. If the selection includes text, controls, or an entire table, they are physically removed from the document and the page is automatically refreshed. (The empty method doesn't delete tables that are only partially selected, however):

' Delete the selected portion of the document 
' when the user presses the "C" key.
Sub Document_onkeypress()
    If window.event.keycode = Asc("C") Then
        document.selection.clear
    End If
End Sub

The last method of the Selection object is createRange, which returns a reference to the TextRange object that describes the text currently selected. I'll explain what to do with such a TextRange object in the next section.

The TextRange Object

The TextRange object represents a portion of the document. This can be the area currently selected by the user or an area defined programmatically. The TextRange object lets you access the contents of a portion of the page—either as HTML source code or as text visible to the user—and exposes several methods that let you define the size and position of the range itself.

You can create a TextRange property from the Selection object, as we've just seen, or you can use the createTextRange methods of the Body object or a Button, TextArea or TextBox element:

Set bodyTxtRange = document.body.createTextRange
Set inputTxtRange = document.all("txtUserName").createTextRange

Properties

The TextRange object exposes only two properties, text and htmlText. The former can set or return the textual contents of the portion of the document defined by the object but doesn't let you specify its formatting. The latter property is read-only and returns the portion of the document in HTML format. For example, the following piece of VBScript code displays the HTML contents of the selected text when the user presses the C key and converts the text to uppercase when the user presses the U key:

Sub Document_onkeypress()
    If window.event.keycode = Asc("C") Then
        MsgBox document.selection.createRange.htmlText
    ElseIf window.event.keycode = Asc("U") Then
        ' Type the following two-line statement as one line.
        document.selection.createRange.text = 
            UCase(document.selection.createRange.text)    
    End If
End Sub

The htmlText property always returns syntactically correct HTML code. For example, if the TextRange object comprises only the starting <B> tag of a portion of bold text, the value returned by this property correctly includes the closing </B> tag, so you can safely reuse it in the same or another document without any problem. The value returned by this property also includes <SCRIPT> tags inside the area.

The text property always returns the characters in a TextRange object, but an assignment to it works only if the area doesn't extend over portions of the document with different attributes.

Methods

The TextArea object exposes 27 methods, but I'll explain just the most useful ones. The first method to get familiar with is select, which makes the TextRange object appear to be selected: It's useful for getting visual feedback about what you're doing to the object.

The moveStart, moveEnd, and move methods change the position of the starting point, the ending point, or both ends of the area. You can move these points by the specified number of characters, words, and whole sentences:

' Extend the selection 10 characters to the right.
Set selRange = document.selection.createRange
selRange.moveEnd "character", 10
' Extend it one word to the left. 
' (Negative values move toward the beginning of the document.)
selRange.moveStart "word", -2
selRange.select
' Extend it one sentence to the right. (The value "1" can be omitted.)
selRange.moveEnd "sentence"
selRange.select
' Restore it as it was.
selRange.move "textedit"

The collapse method reduces the size of a TextRange method to either its start point (if the method's argument is True) or to its ending point (if the argument is False):

selRange.collapse True     ' Reduce the range to its starting point.

The moveToElementText method is useful when you want the TextRange object to move over a particular element in the page. This method works only if the TextRange already includes the element, so you often create a TextRange object from the body element and then shrink it to the desired element, as in the following code:

' Create a TextRange corresponding to the "MyControl" element.
Set range = document.body.createTextRange
range.moveToElementText document.all("MyControl")

You can use the moveToPoint method to have the TextRange point to a given x-y pair of coordinates, typically the mouse coordinates:

' Retrieve the word the user clicked on.
Sub Document_onclick()
    Set range = document.body.createTextRange
    range.moveToPoint window.event.x, window.event.y
    range.expand "word"
    MsgBox range.text
End Sub

Use the findText method to have a TextRange move over a given text string in the page. In its simplest form, this method takes one argument, the string being searched, and returns True if the search succeeds (in which case the range has moved over the searched text). Otherwise, it returns False:

Set range = document.body.createTextRange
If range.findText("ABC") Then
    range.select
Else
    Msgbox "Text not found"
End If

Concerning the remaining methods of the TextRange object, it's worth mentioning scrollIntoView (which ensures that the text range is visible in the browser's window), parentElement (which returns a reference to the element that completely contains the text range), pasteHTML (which replaces the contents of the text range with HTML code), and duplicate (which creates a new TextRange object that points to the same range).

The Table Object

In Dynamic HTML, tables are defined exactly as they are in pure HTML—that is, with the <TABLE> and </TABLE> tag pair and a series of <TR> and <TD> tags. The real difference is that under DHTML a table exposes the rows and cells collections, which let you access individual cells without having to assign them a specific ID attribute. More precisely, the table object exposes a rows collection, and each row object exposes a cells collection. The following piece of code extracts the contents of the table as a tab-delimited string that's ready to be exported to a text file:

Set table = document.all("Table1")
For each thisRow in table.rows
    For each thisCell In thisRow.cells
        text = text & thisCell.innerText & Chr(9)
    Next
    ' Replace the last tab char with a CR-LF pair.
    text = Left(text, Len(text) - 1) & Chr(13) & Chr(10)
Next
MsgBox text

You can directly reference a cell using this syntax:

' Modify the first cell in the third row. (Row/column indices are
' zero-based.)
table.rows(2).cells(0).innerText = "New Value"

Because individual cells don't support the innerHTML property, to modify the attributes of a given cell you must create a TextRange object and use its pasteHTML method instead:

Set thisCell = table.rows(2).cells(0)
Set range = document.body.createTextRange
range.moveToElementText thisCell
range.pasteHTML "<B>New Value in Boldface</B>"

Even more exciting is your ability to add new rows and columns, thanks to the insertRow method of the table object, and the insertCell method of the row object:

' Add a row as the fifth row of the table.
set newRow = table.insertRow(4)
' Insert a cell in the first column, and set its contents.
set newCell = newRow.insertCell(0)
newCell.innerText = "New Cell in Column 1"
' Add other cells, using a more concise syntax.
newRow.insertCell(1).innerText = "New cell in Column 2"
newRow.insertCell(2).innerText = "New cell in Column 3"

You can also delete cells or entire rows using the row object's deleteCell method and the table object's deleteRow method, respectively. The table, row, and cell objects have a few properties in common—such as align, vAlign, and borderColor—that let you format data they contain.